package net.valerijtimakov.homefinances.dao.storages.common.mysql;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import net.valerijtimakov.homefinances.dao.storages.common.mysql.query_builders.DeleteQuery;
import net.valerijtimakov.homefinances.dao.storages.common.mysql.query_builders.InsertQuery;
import net.valerijtimakov.homefinances.dao.storages.common.mysql.query_builders.SelectQuery;
import net.valerijtimakov.homefinances.dao.storages.common.mysql.query_builders.UpdateQuery;
import net.valerijtimakov.homefinances.dao.storages.finances.mysql.IMySqlProvider;
import net.valerijtimakov.homefinances.domain.common.DataItem;
import net.valerijtimakov.homefinances.exceptions.AppExceptionWrongState;
import net.valerijtimakov.homefinances.exceptions.AppPersProviderException;
import net.valerijtimakov.homefinances.exceptions.AppPersistentException;
import net.valerijtimakov.homefinances.exceptions.wrongarg.AppExceptionNullArgument;
import net.valerijtimakov.homefinances.utils.Utils;

public abstract class ItemsStorage {
	
	protected SelectQuery selectQuery = null;
	protected InsertQuery insertQuery = null;
	protected UpdateQuery updateQuery = null;
	protected DeleteQuery deleteQuery = null;
	protected String identityFieldName = "id";
	protected IMySqlProvider provider = null;
	
	public ItemsStorage(IMySqlProvider provider) throws AppExceptionWrongState {
		super();
		if (provider == null) {
			throw new AppExceptionWrongState("Не задан провайдер данных!");
		}
		this.provider = provider;
	}

	protected <T extends DataItem> void parse(ResultSet rs, T item) throws SQLException {
		item.setId(rs.getLong(identityFieldName));
	}
	
	protected void initSelect() {
		selectQuery = new SelectQuery();
		selectQuery.addField(identityFieldName);
		selectQuery.setTableName(getTableName());
	}
	
	protected abstract String getTableName();
	
	protected <T extends DataItem> Map<Long, T> getItemsByIds(long ... id) 
		throws AppPersProviderException, AppExceptionWrongState 
	{
		initSelect();
		addSelectIdentityValues(Utils.longArrToRef(id));
		return getMap();
	}
	
	private void addSelectIdentityValues(Long ... id) {
		selectQuery.getWhere().createListCondBuilder(identityFieldName, Long.class).importFieldValues(id);
	}
	
	@SuppressWarnings("unchecked")
	protected <T extends DataItem> Map<Long, T> getMap() 
		throws AppExceptionWrongState, AppPersProviderException 
	{
		String query = selectQuery.getSql();
		HashMap<Long, T> res = new HashMap<Long, T>();
		if (query.isEmpty()) {
			return res;
		}
		try{
			provider.query(query);
			while (provider.getResultSet().next()){
				DataItem item = createTempItem();
				parse(provider.getResultSet(), item);
				res.put(item.getId(), (T) item);
			}			
			return res;
		} catch (SQLException e) {
			throw new AppPersProviderException(e);
		} finally {
			if(provider!=null) {
				provider.close();
			}
		}
	}
	
	protected abstract DataItem createTempItem();
	
	protected <T extends DataItem>  void addItem(T item, String nullArgErrMsg, String addErrMsg) 
		throws AppExceptionNullArgument, AppExceptionWrongState, AppPersistentException 
	{
		if (item == null) {
			throw new AppExceptionNullArgument(nullArgErrMsg);
		}
		insertQuery = new InsertQuery();
		insertQuery.setTableName(getTableName());
		addData(item);
		long id = insert();
		if (id == IMySqlProvider.UNDEFINED_LAST_INSERT_ID) {
			throw new AppPersistentException(addErrMsg);
		}
		item.setId(id);
	}
	
	protected abstract <T extends DataItem> void addData(T item);
	
	private long insert() throws AppExceptionWrongState, AppPersProviderException {
		String query = insertQuery.getSql();
		if (query.isEmpty()) {
			return IMySqlProvider.UNDEFINED_LAST_INSERT_ID;
		}
		try{
			return provider.insert(query);
		} finally {
			if(provider!=null) {
				provider.close();
			}
		}
	}
	
	protected <T extends DataItem> void update(T item) 
		throws AppPersProviderException, AppExceptionWrongState 
	{
		updateQuery = new UpdateQuery();
		updateQuery.getWhere().createListCondBuilder(identityFieldName, Long.class).addFieldValue(item.getId());
		updateQuery.setTableName(getTableName());
		setUpdatedFields(item);
		update();
	}
	
	protected abstract <T extends DataItem> void setUpdatedFields(T item);
	
	private int update() throws AppPersProviderException, AppExceptionWrongState {
		String query = updateQuery.getSql();
		if (query.isEmpty()) {
			return 0;
		}
		try{
			return provider.update(query);
		} finally {
			if(provider!=null) {
				provider.close();
			}
		}
	}
	
	protected void delete(long ...ids) throws AppPersProviderException, AppExceptionWrongState {
		deleteQuery = new DeleteQuery();
		deleteQuery.getWhere().createListCondBuilder(identityFieldName, Long.class).
			importFieldValues(Utils.longArrToRef(ids));
		deleteQuery.setTableName(getTableName());
		delete(deleteQuery.getSql());
	}
	
	private int delete(String query) throws AppPersProviderException, AppExceptionWrongState {
		if (query.isEmpty()) {
			return 0;
		}
		try{
			return provider.update(query);
		} finally {
			if(provider!=null) {
				provider.close();
			}
		}
	}
	
}
